/*
 * Copyright 2010 Gal Dolber.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.unnison.framework.client.command;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;

import com.unnison.framework.client.command.action.Action;
import com.unnison.framework.client.command.action.Response;
import com.unnison.framework.client.eventbus.EventBus;
import com.unnison.framework.client.logging.Logger;

import java.util.ArrayList;

public class CommandServiceImpl extends Timer implements CommandService {

    private final int delay = 50;

    private final EventBus eventBus;
    private final CommandRpcAsync service;
    private final Logger logger;

    private final ArrayList<Action<Response>> actions = new ArrayList<Action<Response>>();
    private final ArrayList<AsyncCallback<Response>> callbacks = new ArrayList<AsyncCallback<Response>>();

    // Requests pools
    private final ArrayList<CommandBatchRequestUnit> singleRequestPool = new ArrayList<CommandBatchRequestUnit>();
    private final ArrayList<CommandSingleRequestUnit> batchRequestPool = new ArrayList<CommandSingleRequestUnit>();

    @Inject
    public CommandServiceImpl(EventBus eventBus, CommandRpcAsync service, Logger logger) {
        this.eventBus = eventBus;
        this.service = service;
        if (!GWT.isScript()) {
            this.logger = logger;
        } else {
            this.logger = null;
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public <A extends Action<R>, R extends Response> void execute(A action, AsyncCallback<R> callback) {
        if (!GWT.isScript()) {
            logger.log("COMMAND " + action.toString());
        }
        actions.add((Action<Response>) action);
        callbacks.add((AsyncCallback<Response>) callback);

        cancel();
        schedule(delay);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <A extends Action<R>, R extends Response> void executeLater(A action, AsyncCallback<R> callback) {
        if (!GWT.isScript()) {
            logger.log("COMMAND " + action.toString());
        }
        actions.add((Action<Response>) action);
        callbacks.add((AsyncCallback<Response>) callback);
    }

    /**
     * Get a single request unit.
     */
    private CommandBatchRequestUnit getBatchRequestUnit() {
        if (singleRequestPool.size() > 0) {
            CommandBatchRequestUnit poolInstance = singleRequestPool.get(0);
            singleRequestPool.remove(0);
            return poolInstance;
        } else {
            return new CommandBatchRequestUnit(eventBus, service, new CommandRequestUnitFinish<CommandBatchRequestUnit>() {
                @Override
                public void onFinish(CommandBatchRequestUnit requestUnit) {
                    singleRequestPool.add(requestUnit);
                }
            });
        }
    }

    /**
     * Get a batch request unit.
     */
    private CommandSingleRequestUnit getSingleRequestUnit() {
        if (batchRequestPool.size() > 0) {
            CommandSingleRequestUnit poolInstance = batchRequestPool.get(0);
            batchRequestPool.remove(0);
            return poolInstance;
        } else {
            return new CommandSingleRequestUnit(eventBus, service, new CommandRequestUnitFinish<CommandSingleRequestUnit>() {
                @Override
                public void onFinish(CommandSingleRequestUnit requestUnit) {
                    batchRequestPool.add(requestUnit);
                }
            });
        }
    }

    @Override
    public void run() {
        if (!actions.isEmpty()) {
            if (actions.size() > 1) {
                getBatchRequestUnit().execute(actions, new ArrayList<AsyncCallback<Response>>(callbacks));
            } else {
                getSingleRequestUnit().execute(actions.get(0), callbacks.get(0));
            }

            actions.clear();
            callbacks.clear();
        }
    }
}
